using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using TransactionManager = javax.transaction.TransactionManager;

	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using RelationshipTypeData = org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
	using IdGenerator = org.neo4j.kernel.impl.persistence.IdGenerator;
	using PersistenceManager = org.neo4j.kernel.impl.persistence.PersistenceManager;
	using TransactionFailureException = org.neo4j.kernel.impl.transaction.TransactionFailureException;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

	internal class RelationshipTypeHolder
	{
		private ArrayMap<string, int> relTypes = new ArrayMap<string, int>(5, true, true);
		private Map<int, string> relTranslation = new ConcurrentHashMap<int, string>();

		private readonly TransactionManager transactionManager;
		private readonly PersistenceManager persistenceManager;
		private readonly IdGenerator idGenerator;

		internal RelationshipTypeHolder(TransactionManager transactionManager, PersistenceManager persistenceManager, IdGenerator idGenerator)
		{
			this.transactionManager = transactionManager;
			this.persistenceManager = persistenceManager;
			this.idGenerator = idGenerator;
		}

		internal virtual void addRawRelationshipTypes(RelationshipTypeData[] types)
		{
			for (int i = 0; i < types.Length; i++)
			{
				relTypes.put(types[i].Name, types[i].getId());
				relTranslation.put(types[i].getId(), types[i].Name);
			}
		}

		internal virtual void addRawRelationshipType(RelationshipTypeData type)
		{
			relTypes.put(type.Name, type.getId());
			relTranslation.put(type.getId(), type.Name);
		}

		public virtual RelationshipType addValidRelationshipType(string name, bool create)
		{
			if (relTypes.get(name) == null)
			{
				if (!create)
				{
					return null;
				}
				int id = createRelationshipType(name);
				relTranslation.put(id, name);
			}
			else
			{
				relTranslation.put(relTypes.get(name), name);
			}
			return new RelationshipTypeImpl(name);
		}

		internal virtual bool isValidRelationshipType(RelationshipType type)
		{
			return relTypes.get(type.name()) != null;
		}

		private class RelationshipTypeImpl : RelationshipType
		{
			private string name;

			internal RelationshipTypeImpl(string name)
			{
				Debug.Assert(name != null);
				this.name = name;
			}

			public virtual string name()
			{
				return name;
			}

			public override string ToString()
			{
				return name;
			}

			public override bool Equals(object o)
			{
				if (!(o is RelationshipType))
				{
					return false;
				}
				return name.Equals(((RelationshipType) o).name());
			}

			public override int GetHashCode()
			{
				return name.GetHashCode();
			}
		}

	// TODO: this should be fixed to run in same thread
		private class RelTypeCreater : Thread
		{
			private bool success = false;
			private string name;
			private int id = -1;

			internal RelTypeCreater(string name) : base()
			{
				this.name = name;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual bool succeded()
			{
				return success;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			internal virtual int getRelTypeId()
			{
				return id;
			}

			[MethodImpl(MethodImplOptions.Synchronized)]
			public virtual void run()
			{
				try
				{
					transactionManager.begin();
					id = idGenerator.nextId(typeof(RelationshipType));
					persistenceManager.createRelationshipType(id, name);
					transactionManager.commit();
					success = true;
				}
				catch (System.Exception t)
				{
					t.printStackTrace();
					try
					{
						transactionManager.rollback();
					}
					catch (System.Exception tt)
					{
						tt.printStackTrace();
					}
				}
				finally
				{
					this.notify();
				}
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		private int createRelationshipType(string name)
		{
			int id = relTypes.get(name);
			if (id != null)
			{
				return id;
			}
			RelTypeCreater createrThread = new RelTypeCreater(name);
			lock (createrThread)
			{
				createrThread.Start();
				while (createrThread.isAlive())
				{
					try
					{
						createrThread.wait(50);
					}
					catch (InterruptedException e)
					{
						Thread.interrupted();
					}
				}
			}
			if (createrThread.succeded())
			{
				addRelType(name, createrThread.getRelTypeId());
				return createrThread.getRelTypeId();
			}
			throw new TransactionFailureException("Unable to create relationship type " + name);
		}

		internal virtual void addRelType(string name, int id)
		{
			relTypes.put(name, id);
		}

		internal virtual void removeRelType(string name)
		{
			relTypes.Remove(name);
		}

		internal virtual void removeRelType(int id)
		{
			string name = relTranslation.Remove(id);
			if (name != null)
			{
				relTypes.Remove(name);
			}
		}

		internal virtual int getIdFor(RelationshipType type)
		{
			return relTypes.get(type.name());
		}

		internal virtual RelationshipType getRelationshipType(int id)
		{
			string name = relTranslation.get(id);
			if (name != null)
			{
				return new RelationshipTypeImpl(name);
			}
			return null;
		}

		public virtual IEnumerable <RelationshipType> getRelationshipTypes()
		{
			List<RelationshipType> relTypeList = new List<RelationshipType>();
			foreach (string name in relTypes.Keys)
			{
				relTypeList.Add(new RelationshipTypeImpl(name));
			}
			return relTypeList;
		}

		internal virtual void clear()
		{
			relTypes = new ArrayMap<string, int>();
			relTranslation = new Dictionary<int, string>();
		}
	}
}